/*
 * @Author: wujiedong
 * @Date: 2021-08-25 14:10:11
 * @LastEditTime: 2021-10-07 16:40:56
 * @LastEditors: Please set LastEditors
 * @Description: vue简易版,实现数据响应式,数据更新会自动更新页面dom
 * @FilePath: \common-vue\kVue\KVue.js
 */
/*
1) KVue: 构建KVue构造函数
2) Observer: 完成数据(构造函数中传入的data)响应式(需要区分对象和数组)
3) Compile: 将页面特有kvue表达式进行编译,可正常在html页面显示
4) Watcher: 执行更新函数(更新dom)
5) Dep: 管理多个watcher
*/

// eslint-disable-next-line no-unused-vars
class KVue {
  constructor(options = {}) {
    this.$options = options
    this.$data = options.data
    this.$methods = options.methods
    // 响应式
    observe(this.$data)
    // 代理
    proxy(this)
    // 编译
    // eslint-disable-next-line no-new
    new Compile(this.$options.el, this)
  }
}

// option中data数据完成响应式
function observe (obj) {
  if (typeof obj !== 'object' || obj === null) return // 递归的出口
  // eslint-disable-next-line no-new
  new Observer(obj)
}

// 观察者: 负责完成data数据的响应式
class Observer {
  constructor(value) {
    this._value = value

    if (Array.isArray(this._value)) {
      // todo
    } else {
      this.walk(this._value)
    }
  }

  walk (obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}

function defineReactive (obj, key, value) {
  // 递归,作用: obj里面第二层开始是对象
  observe(value)
  // 创建dep
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    // 闭包的应用
    get () {
      Dep.target && dep.addDep(Dep.target)
      return value
    },
    set (newValue) {
      if (newValue === value) {
        return
      }
      console.log('set key', key)
      console.log('set value', newValue)
      // 递归,作用: obj中对key赋值的时候,直接赋值,而没有使用.进行赋值,newValue是对象
      observe(newValue)
      value = newValue
      // watchers.forEach(watch => watch.update())
      dep.notify()
    }
  })
}

// 将KVue中data数据做一个代理,原本通过vm.data.count访问,通过代理可以直接使用vm.count来访问
function proxy (vm) {
  console.log('proxy vm', vm)
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get () {
        return vm.$data[key]
      },
      set (v) {
        console.log('proxy set vm', vm)
        vm.$data[key] = v
      }
    })
  })
}

// 编译页面中k-*指令
class Compile {
  constructor(el, vm) {
    this.$el = document.querySelector(el)
    this.$vm = vm
    this.compileDom(this.$el)
    Compile.target = 'target222'
  }

  compileDom (el) {
    const nodes = el.childNodes
    nodes.forEach(node => {
      if (node.nodeType === 1) { // 元素,处理事件,自定义指令等
        const attrs = node.attributes

        Array.from(attrs).forEach(attr => {
          const attrName = attr.name
          const exp = attr.value
          if (attrName.endsWith('click')) { // 绑定节点中k-click单击事件,将vm实例绑定到单击事件中
            node.addEventListener('click', this.$vm.$methods[exp].bind(this.$vm))
          } else if (attrName.endsWith('text')) { // 将节点中k-text中数据渲染到节点中去
            this.$vm[exp] && this.text(node, exp)
          } else if (attrName.endsWith('html')) { // 将节点中k-html中数据渲染到节点中去,还需要渲染对应html
            this.$vm[exp] && this.html(node, exp)
          }
        })
      } else if (this.isInter(node)) { // 动态文本,直接赋值文本即可
        this.compileText(node)
      }

      // 递归 dom里面还有子
      if (node.childNodes) {
        this.compileDom(node)
      }
    })
  }

  // 所有节点解析 都走这个方法
  update(node, exp, updateFnName) {
    // 1,初始化
    const updateFn = this[`${updateFnName}Updater`]
    updateFn && updateFn(node, this.$vm[exp])
    // 2,更新
    // eslint-disable-next-line no-new
    new Watcher(this.$vm, exp, function(val) {
      updateFn && updateFn(node, val)
    })
  }

  html(node, exp) {
    this.update(node, exp, 'html')
  }

  htmlUpdater(node, expValue) {
    node.innerHTML = expValue
  }

  text(node, exp) {
    this.update(node, exp, 'text')
  }

  textUpdater(node, expValue) {
    node.textContent = expValue
  }

  compileText (node) {
    const exp = RegExp.$1
    this.update(node, exp, 'compileText')
  }

  compileTextUpdater(node, expValue) {
    node.textContent = expValue
  }

  isInter (node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
}

const watchers = []
// 监听器:负责依赖更新
class Watcher {
  constructor(vm, key, updateFn) {
    this.vm = vm
    this.key = key
    this.updateFn = updateFn

    watchers.push(this)

    Dep.target = this
    // eslint-disable-next-line no-unused-expressions
    this.vm[this.key]
    Dep.target = null
  }

  // 数据更新,被dep的实例调用
  update() {
    // 执行实际更新操作
    this.updateFn.call(this.vm, this.vm[this.key])
  }
}

class Dep {
  constructor() {
    this.deps = []
  }

  addDep(dep) {
    this.deps.push(dep)
  }

  // 外部只负责通知,具体实现不管
  notify() {
    this.deps.forEach(dep => dep.update())
  }
}
// export default KVue
